// Copyright 2021 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package run

import (
	"fmt"

	bqpb "go.chromium.org/luci/cv/api/bigquery/v1"
	"go.chromium.org/luci/cv/internal/gerrit"
)

// Mode dictates the behavior of this Run.
//
// The end goal is to have arbitrary user-defined Mode names.
// For now, CQDaemon/LUCI CV operates with just 3 pre-defined modes,
// whose values are fixed based on legacy CQ BQ export.
type Mode string

const (
	// DryRun triggers configured Tryjobs, but doesn't submit.
	DryRun Mode = "DRY_RUN"
	// FullRun is DryRun followed by submit.
	FullRun Mode = "FULL_RUN"
	// QuickDryRun is like DryRun but different thus allowing either different or
	// faster yet less thorough Tryjobs.
	QuickDryRun Mode = "QUICK_DRY_RUN"
	// NewPatchsetRun is triggered by uploading a new patchset.
	// It only runs tryjobs explicitly allowed in this mode.
	NewPatchsetRun Mode = "NEW_PATCHSET_RUN"
)

func (m Mode) Valid() bool {
	switch m {
	case DryRun, FullRun, QuickDryRun, NewPatchsetRun:
		return true
	default:
		return false
	}
}

// BQAttemptMode returns corresponding value for legacy CQ BQ export.
func (m Mode) BQAttemptMode() bqpb.Mode {
	switch m {
	case DryRun:
		return bqpb.Mode_DRY_RUN
	case FullRun:
		return bqpb.Mode_FULL_RUN
	case QuickDryRun:
		return bqpb.Mode_QUICK_DRY_RUN
	case NewPatchsetRun:
		// TODO(robertocn): Remove this hack, either by adding a supported mode in bq
		// export, or by filtering out NPRs from the response to CQD when it calls
		// FetchActiveRuns().
		return bqpb.Mode_MODE_UNSPECIFIED
	default:
		panic(fmt.Sprintf("unknown RunMode %q", m))
	}
}

// ModeFromBQAttempt returns Mode from CQ BQ export.
func ModeFromBQAttempt(m bqpb.Mode) (Mode, error) {
	switch m {
	case bqpb.Mode_DRY_RUN:
		return DryRun, nil
	case bqpb.Mode_FULL_RUN:
		return FullRun, nil
	case bqpb.Mode_QUICK_DRY_RUN:
		return QuickDryRun, nil
	default:
		return "", fmt.Errorf("unknown Attempt mode %d %s", m, m)
	}
}

// DefaultAllowedModes is the list of RunModes implied by an empty ModeAllowList
// in tryjob verifier configuration.
func DefaultAllowedModes() []string {
	return []string{string(DryRun), string(FullRun), string(QuickDryRun)}
}

// GerritMessageTag returns corresponding value for the Gerrit message tag.
//
// Tag value set by CQDaemon sadly became part of the API surface, see
// https://crbug.com/1263652.
// TODO(crbug.com/1264046): delete this API surface after users migrate away.
func (m Mode) GerritMessageTag() string {
	const prefix = "autogenerated:cq:"
	switch m {
	case DryRun:
		return prefix + "dry-run"
	case FullRun:
		return prefix + "full-run"
	case QuickDryRun:
		return prefix + "quick-dry-run"
	case NewPatchsetRun:
		return prefix + "new-patchset-run"
	default:
		panic(fmt.Sprintf("unknown RunMode %q", m))
	}
}

// GerritNotifyTargets gets the default set of user roles to notify depending on
// the given RunMode.
//
// In general, for CQ runs (dry or else), it would notify the owner of the CL
// and those who triggered the run by voting on the cq label; and in the case of
// Runs triggered by uploading a new patchset, it would notify the owner of the
// CL and the uploader of the given patchset.
func (m Mode) GerritNotifyTargets() gerrit.Whoms {
	switch m {
	case NewPatchsetRun:
		return gerrit.Whoms{gerrit.Owner, gerrit.PSUploader}
	case DryRun, FullRun, QuickDryRun:
		return gerrit.Whoms{gerrit.Owner, gerrit.CQVoters}
	default:
		panic(fmt.Errorf("unsupported mode %s", m))
	}
}
